
#include "Network.h"
#include "Print.hpp"



namespace {
	std::string readMessage(boost::asio::ip::tcp::socket& socket,
			boost::system::error_code& error) {
		size_t str_len;
		socket.read_some(boost::asio::buffer(&str_len, sizeof(str_len)), error);

		if (error == boost::asio::error::eof)
			return "";
		else if (error)
			return "";

		std::stringstream out;
		size_t len = 0;
		while (len < str_len) {
			std::vector<char> buf(str_len - len);
			size_t l = socket.read_some(boost::asio::buffer(buf), error);
			len += l;

			if (error == boost::asio::error::eof)
				return out.str();
			else if (error)
				return out.str();

			out.write(buf.data(), len);
		}
		return out.str();
	}

	void sendMessage(const std::string message,
			boost::asio::ip::tcp::socket& socket,
			boost::system::error_code& error) {

		size_t len = message.size();
		boost::asio::write(socket, boost::asio::buffer(&len, sizeof(len)), error);
		if (error)
			return;
		boost::asio::write(socket, boost::asio::buffer(message), error);
		if (error)
			return;
	}

	typedef boost::asio::ip::tcp::socket socket_t;
	typedef boost::shared_ptr<socket_t> socket_ref;
	typedef std::map<std::string, socket_ref> Hosts;
	typedef boost::system::error_code error_code;
	typedef boost::thread thread_t;
	typedef boost::shared_ptr<thread_t> thread_ref;
	typedef std::map<std::string, thread_ref> Threads;
	typedef boost::asio::io_service ios_t;
	typedef boost::shared_ptr<ios_t> ios_ref;

}

#define LOCK_HOSTS boost::mutex::scoped_lock host_lock(host_mtx);

Network::Network(const Address& addr) :
		id(addr.port), teamid(addr.str()), stop_server(false) {

}

void Network::runServer() {
	stop_server = false;
	try {

		int port;
		std::stringstream s(id);
		s >> port;
		boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(),
				port);
		boost::asio::ip::tcp::acceptor acceptor(io_service, endpoint);

		//std::cout << "Server ready" << std::endl; // 2

		boost::system::error_code error;

		while (!stop_server) {
			socket_ref socket(new socket_t(io_service));
			acceptor.accept(*(socket.get()), error); // 3

			if (!error) {

				std::string port = readMessage(*(socket.get()), error);

				if (error) {
					fireOnConnect("", error);
					socket->close();
				} else {

					boost::asio::ip::tcp::endpoint remote_ep =
							socket->remote_endpoint();
					boost::asio::ip::address remote_ad = remote_ep.address();
					std::string ip = remote_ad.to_string() + ":" + port;
					addHost(ip, socket);
					fireOnConnect(ip, error);

				}

			} else {
				fireOnConnect("", error);
			}
		}
	} catch (std::exception& e) // 6
	{
		std::cerr << "Exception: " << e.what() << std::endl;
	}

}

void Network::connectTo(std::string host, std::string id,
		boost::system::error_code& error) {
	try {

		boost::asio::ip::tcp::resolver resolver(io_service);
		boost::asio::ip::tcp::resolver::query query(host.c_str(), id.c_str());
		boost::asio::ip::tcp::resolver::iterator endpoint_iterator =
				resolver.resolve(query);
		boost::asio::ip::tcp::resolver::iterator end;

		socket_ref socket(new socket_t(io_service));
		error = boost::asio::error::host_not_found;
		while (error && endpoint_iterator != end) {
			socket->close();
			socket->connect(*endpoint_iterator++, error);
		}
		if (error) {
			fireOnConnect(id, error);
			return;
		}

		sendMessage(this->id, *(socket.get()), error);

		if (error) {
			fireOnConnect(id, error);
			return;
		}

		std::string ip = host + ":" + id;
		addHost(ip, socket);
		fireOnConnect(ip, error);

	} catch (std::exception& e) {
		std::cerr << e.what() << std::endl;
	}
}

int Network::send(const std::string& id, std::string message,
		error_code& error) {
	socket_ref socket;
	{
		LOCK_HOSTS
		if(hosts.find(id)==hosts.end())
		return 0;
		socket=hosts[id];
	}
	sendMessage(message, *(socket.get()), error);
	if (error) {
		return 0;
	}
	return 1;
}
void Network::send(const Team& ids, std::string message) {
	error_code error;
	for (size_t i = 0; i < ids.size(); i++) {
		send(ids[i].str(), message, error);
	}
}
void Network::send(const std::vector<std::string>& ids, std::string message) {
	error_code error;
	for (size_t i = 0; i < ids.size(); i++) {
		send(ids[i], message, error);
	}
}
void Network::send(const std::set<std::string>& ids, std::string message) {
	error_code error;
	for (std::set<std::string>::const_iterator i = ids.begin(); i != ids.end();
			i++) {
		send((*i), message, error);
	}
}
void Network::broadcast(std::string message) {
	std::vector<std::string> ids = getConnections();
	send(ids, message);
}

std::string Network::read(const std::string& id, error_code& error) {
	socket_ref socket;
	{
		LOCK_HOSTS
		if(hosts.find(id)==hosts.end())
		return "";
		socket=hosts[id];
	}
	std::string mes = readMessage(*(socket.get()), error);
	if (error)
		return "";
	return mes;
}

void Network::addHost(const std::string& id, socket_ref socket) {
	LOCK_HOSTS
	if(hosts.find(id)!=hosts.end())
	hosts[id]->close();
	hosts[id] = socket;
	threads[id] = thread_ref( new thread_t(boost::bind(& Network::reading, this, id)) );
	//std::cout<<"New client connected. client id = "<<id<<std::endl;
}

void Network::removeHost(const std::string& id) {
	LOCK_HOSTS
	if(hosts.find(id)!=hosts.end()) {
		hosts.erase(id);
		threads.erase(id);
	}
	//std::cout<<"Removed connection. client id = "<<id<<std::endl;
}
bool Network::threadStop(const std::string& id) {
	LOCK_HOSTS
	if(threads.find(id)!=threads.end()) return false;
	return true;
}

void Network::reading(std::string id) {
	error_code error;
	while (!stop_server && !threadStop(id)) {
		std::string mes = read(id, error);
		if (mes == "" || error) {
			fireOnMessage(id, "", error);
			break;
		}
		//std::cout<<"New Message: "<<mes<<" from "<<id<<std::endl;
		fireOnMessage(id, mes, error);
	}
	hosts[id]->close();
	removeHost(id);
	fireOnDisconnect(id, error);
}

size_t Network::setCallbacks(onConnect_t _onConnect,
		onDisconnect_t _onDisconnect, onMessage_t _onMessage) {
	static size_t setid = 0;
	setid++;
	onConnect[setid] = _onConnect;
	onDisconnect[setid] = _onDisconnect;
	onMessage[setid] = _onMessage;
	return setid;
}
void Network::removeCallbacks(size_t setid) {
	onConnect.erase(setid);
	onDisconnect.erase(setid);
	onMessage.erase(setid);
}

void Network::connectToTeam(const Team& team) {
	error_code error;
	for (size_t i = 0; i < team.size(); i++)
		if (team[i].port != id) {
			connectTo(team[i].host, team[i].port, error);
			if (error) {
				//std::cout << "Connection error with " << team[i].str() << std::endl;
			}
		}
}

void Network::connect(const Team& team){
	connectToTeam(team);
	runServer();
}
std::vector<std::string> Network::getConnections() {
	LOCK_HOSTS
	std::vector<std::string> team;
	for(Hosts::iterator i=hosts.begin(); i!=hosts.end(); i++) {
		team.push_back(i->first);
	}
	return team;
}
std::set<std::string> Network::getConnectionsSet() {
	LOCK_HOSTS
	std::set<std::string> team;
	for(Hosts::iterator i=hosts.begin(); i!=hosts.end(); i++) {
		team.insert(i->first);
	}
	return team;
}
bool Network::isMe(std::string _id) const {
	return teamid == _id;
}
bool Network::MeLessThen(std::string _id)const{
	return teamid < _id;
}


void Event_OnConnection(std::string id, error_code error){
	std::cout<<"EVENT<CONNECTION>: id="<<id<<", error="<<error<<std::endl;
}
void Event_OnDisconnection(std::string id, error_code error){
	std::cout<<"EVENT<DISCONNECTION>: id="<<id<<", error="<<error<<std::endl;
}
void Event_OnMessage(std::string id, std::string message, error_code error){
	std::cout<<"EVENT<CONNECTION>: id="<<id<<", error="<<error<<", message="<<message<<std::endl;
}








#undef LOCK_HOSTS
